<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>logserver</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<style type="text/css">
pre {
	overflow: scroll;
	position: absolute;
	bottom: 1px;
	width: 98%;
}
.nav>li>a {
	padding-left: 12px;
	padding-right:12px;
}
</style>
</head>
<body>
	<div class="container-fluid">
		<div class="tabs-container">
			<ul class="nav nav-tabs">
				<!-- <li v-for="tab in tabs" :class="{active:tabName==tab}"><a data-toggle="tab" href="#tab-{{tab}}">{{tab}}</a></li> -->
				<li class="active"><a data-toggle="tab" href="#tab-tail" title="实时跟踪最新日志，使用WebSocket技术">tail</a></li>
				<li class=""><a data-toggle="tab" href="#tab-page" title="分页查看最近日志，支持搜索">page</a></li>
				<li class=""><a data-toggle="tab" href="#tab-logs" title="下载最近日志文件">logs</a></li>
				<li class="" v-if="files"><a href="files" @dblclick="open('files')" target="_blank" title="nginx浏览">files</a></li>
				<li class=""><a href="https.html" @dblclick="open('https.html')" target="_blank" title="https证书">https</a></li>
				<li class=""><a href="logger.html" @dblclick="open('logger.html')" target="_blank" title="logger管理">logger</a></li>
			</ul>
			<div class="tab-content">
				<div id="tab-tail" class="tab-pane active">
					<div class="panel-body">
						<div class="row">
							<div class="col-md-4 col-xs-6" style="padding:0">
								<input class="form-control" placeholder="filter" id="filter">
							</div>
							<div class="col-md-2 col-xs-6" style="padding:0">
							    <div class="btn-group" data-toggle="buttons">
							        <label  class="btn btn-default" onclick="filterLog=!$('#filterLog').is(':checked');console.log('filterLog: '+filterLog)">
							            <input type="checkbox" id="filterLog"> 过滤
							        </label>
							        <label class="btn btn-default" onclick="scrollLock=!$('#scrollLog').is(':checked');console.log('scrollLock: '+scrollLock)">
							            <input type="checkbox" id="scrollLog"> 锁定
							        </label>
							        <label class="btn btn-default" onclick="$('#tail')[0].innerHTML='';console.log('clearLog: lines='+lines);lines=500">清空</label>
							    </div>
							</div>
						</div>
						<div class="row">
							<pre id="tail" style="top: 99px" ondblclick="scrollLock=!scrollLock;console.log('scrollLock: '+scrollLock)"></pre>
						</div>
					</div>
				</div>
				<div id="tab-page" class="tab-pane">
					<div class="panel-body">
						<div class="row">
							<div class="col-md-4 col-xs-4" style="padding:0">
								<input class="form-control" @keyup.enter="search" placeholder="search" id="search">
							</div>
							<div class="col-md-2 col-xs-2" style="padding:0">
								<select class="form-control" @change="gotoPage" style="padding:0">
									<option v-for="page in pager.others" value="{{page[0]}}">第 {{page[0]}} 页有 {{page[1]}} 次</option>
								</select>
							</div>
							<div class="col-xs-6 btn-group" style="padding:0;overflow:hidden">
						    	<a class="btn" style="padding-left:1px;padding-right:1px" @click="first">首页</a>
						    	<a class="btn" style="padding-left:1px;padding-right:1px" @click="prev">上页</a>
						    	<input style="width:30px" @keyup.enter="gotoPage" placeholder="{{pager.totalPages}}" v-model="pager.currentPage" value="{{pager.currentPage}}"/>
								<a class="btn" style="padding-left:1px;padding-right:1px" @click="next">下页</a>
								<a class="btn" style="padding-left:1px;padding-right:1px" @click="last">尾页</a>
								<a class="btn" style="padding-left:1px;padding-right:1px" @click="search">Q</a>
							</div>
						</div>
						<div class="row" style="overflow-x:auto;overflow-y:hidden">
							<a v-for="log in logs" class="btn" style="display:inline" @click="gotoLog">{{log}}</a>
						</div>
						<div class="row">
							<pre style="top: 123px" id="properties"></pre>
						</div>
					</div>
				</div>
				<div id="tab-logs" class="tab-pane">
					<div class="panel-body">
						<div class="row">
							<input class="form-control" @keyup.enter="search" placeholder="search">
						</div>
						<a v-for="log in logs" class="btn" href="logs/{{log}}">{{log}}</a>
						<div class="row">
							<textarea rows="18" class="form-control" placeholder="mybatis log：&#13;&#10; ==>  Preparing: &#13;&#10; ==> Parameters:" v-model="mybatis.log"></textarea>
						</div>
						<a class="btn" @click="log2sql">log2sql</a>
						<div class="row">
							<textarea rows="8" class="form-control" v-model="mybatis.sql" readonly></textarea>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div>
</body>
<script src="https://cdn.staticfile.org/vue/1.0.18/vue.min.js"></script>
<script src="https://cdn.staticfile.org/vue-resource/0.7.0/vue-resource.min.js"></script>
<script src="https://cdn.staticfile.org/reconnecting-websocket/1.0.0/reconnecting-websocket.min.js"></script>
<script src="https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.6/js/bootstrap.min.js"></script>
<!-- <script src="https://eshengsky.github.io/lajax/dist/build.min.js"></script> -->
<script src="lajax.min.js"></script>
<script src="logserver.js"></script>
<script type="text/javascript">
	new Vue({
		el : '.container-fluid',
		data: {
			logs: ['all.logs','all.logs.2019-10-09'], //日志文件列表，查询接口log?type=list&search=
			log: 'all.logs',  //当前浏览日志文件
			searchStr: '',  //搜索字符串
			files: false,
			pager: {  //分页查询日志，查询接口log?type=page&log=&currentPage=，totalRows缓存总函数，search搜索出现页码
				totalRows: 0,
				pageSize: 1000,  //默认每页1000行日志
				currentPage: 1,
				totalPages: 0,
				properties: '', //日志分页文本
				others: []  //搜索出现页码列表
			},
			mybatis: {
				log: '',
				sql: ''
			}
		},
		methods: {
			first: function(){
				this.page(1);
			},
			prev: function(){
				this.page(parseInt(this.pager.currentPage) - 1);
			},
			next: function(){
				this.page(parseInt(this.pager.currentPage) + 1);
			},
			last: function(){
				this.page(this.pager.totalPages);
			},
			gotoPage: function(e){
				this.page(parseInt(e.target.value))
			},
			gotoLog: function(e){
				this.log = e.target.text;
				this.pager.totalRows = 0;
				this.pager.others = [];
				this.first();
			},
			page: function(num){
				if(num<1 || this.pager.totalPages<1) num = 1;
				else if(num>this.pager.totalPages) num = this.pager.totalPages;
				//console.log(this.log+" => page: "+num);
				this.pager.currentPage = num;
				this.$http({method:'POST', url:'log?type=page&log='+this.log
						+"&totalRows="+(this.pager.totalRows>0?this.pager.totalRows:'')
						+"&pageSize="+this.pager.pageSize
						+"&currentPage="+num
						+"&search="+(this.pager.others.length>0?'':this.searchStr)}).then(function(response){
					if(this.pager.totalRows<1){
						//this.pager = response.data;
						this.pager.totalRows = response.data.totalRows;
						this.pager.totalPages = response.data.totalPages;
						//搜索出现页码只需查询一次，当totalRows=0时查询并获取结果
						this.pager.others = !response.data.others ? [] : response.data.others;
						if(this.pager.others.length > 0 && this.pager.others[0][0] > 1) {
							this.page(this.pager.others[0][0]);
						}
					}else{
						if(this.pager.totalPages>1 && num==this.pager.totalPages){
							this.pager.totalRows = response.data.totalRows;
							this.pager.totalPages = response.data.totalPages;
						}
					}
					$('#properties').html(!response.data.properties ? '' : !this.searchStr ? response.data.properties.replace(RegExp("<",'g'),"&lt;").replace(RegExp(">",'g'),"&gt;") : response.data.properties.replace(RegExp("<",'g'),"&lt;").replace(RegExp(">",'g'),"&gt;").replace(RegExp(this.searchStr,'g'),'<code style="color:red;font-weight:bold">'+this.searchStr+'</code>'));
					//输出调试信息
					var a=JSON.parse(JSON.stringify(this.pager));
					delete a.properties;
					//console.log(JSON.stringify(a))
				})
			},
			search: function(e){
				var str = $('#search').val();
				this.searchStr = !str ? '' : str;
				this.doSearch();
			},
			doSearch: function(){
				this.$http({method:'POST', url:'log?type=list&search='+this.searchStr}).then(function(response){
					this.logs = response.data;
					if(this.logs.length > 0) {
						this.log =this.logs[0];
						this.pager.totalRows = 0;
						this.pager.others = [];
						this.first();
					}else{
						this.log = 'all.logs';
						this.pager.totalRows = 0;
						this.pager.properties = '';
						this.pager.others = [];
					}
				})
			},
			open: function(page){
				window.open(page, '_blank');
			},
			log2sql: function(){
				this.mybatis.sql = '';
				var lines = this.mybatis.log.split(/[\r\n]+/);
				var preparing = "Preparing: ", parameters = "Parameters: ";
				lines.forEach((line,index)=>{
					if(line && line.indexOf('==>')>0) {
						var p1 = line.indexOf(preparing), p2 = line.indexOf(parameters);
						if(p1 > 0) {
							var sql = line.substring(p1 + preparing.length);
							if(!sql.endsWith(';')) sql += ';'
							this.mybatis.sql += (!this.mybatis.log ? '' : '\n') + sql;
						}else if(p2 > 0){
							var params = line.substring(p2 + parameters.length);
							params = params.split(',')
							params.forEach((param,index)=>{
								param = param.substring(0,param.indexOf('(')).trim();
								p1 = this.mybatis.sql.indexOf('?'), p2 = this.mybatis.sql.lastIndexOf('LIMIT', p1);
								if(p1 > 0) {
									if(p2 > 0 && p1-p2 < 10) {//LIMIT ? and LIMIT ?, ?
										this.mybatis.sql = this.mybatis.sql.replace('?', param);
									}else{
										this.mybatis.sql = this.mybatis.sql.replace('?', '\'' + param + '\'');
									}
								}
							})
						}
					}
				})
			}
		},
		ready: function(){
			this.$http({method:'POST', url:'log?type=props&key=files'}).then(function(response){
				this.files = response.data.files && response.data.files=='true';
			})
			this.doSearch();
		}
	});
	var wsurl = (location.href.replace('index.html','')+"tail").replace('http','ws');
	var socket = new ReconnectingWebSocket(wsurl), scrollLock = false, filterLog = false, lines = 500, maxLines = 1000;
	socket.onmessage = function(event) {
		var chat = document.getElementById('tail'), prefix = lines>0||scrollLock||chat.innerHTML.indexOf('\n')==-1 ? chat.innerHTML : chat.innerHTML.substr(chat.innerHTML.indexOf('\n')+1);
		var search = $('#filter').val()||$('#search').val(), hasSearch = search&&event.data.indexOf(search)>=0, text = !search ? event.data.replace(RegExp("<",'g'),"&lt;").replace(RegExp(">",'g'),"&gt;") : event.data.replace("<","&lt;").replace(">","&gt;").replace(RegExp(search,'g'),'<code style="color:red;font-weight:bold">'+search+'</code>');
		if(filterLog && !hasSearch) return;
		var rows = prefix.split('\n');
		if(scrollLock && rows.length > maxLines) {
			rows = rows.slice(rows.length - maxLines);
			prefix = rows.join('\n');
		}
		chat.innerHTML = prefix + text + "\n";
		if(scrollLock==false) chat.scrollTop = chat.scrollHeight;
		if(--lines % 100 ==0) console.log('lines: '+lines+", rows: "+rows.length);
	};	
</script>
</html>